#include "mqtt_client_implement.h"

#include <MQTTClient.h>
#include <cstring>
#include <iostream>
#include <memory>

MQTTClientImplement::MQTTClientImplement(std::string server_uri,
                                         std::string client_id,
                                         std::string username,
                                         std::string password,
                                         std::string ca_file,
                                         std::string cert_file,
                                         std::string key_file,
                                         std::string key_file_password,
                                         int         connect_timeout_s) :
    server_uri_(server_uri), client_id_(client_id), client_username_(username), client_password_(password),
    client_ssl_filepath_ca_(ca_file), client_ssl_filepath_client_cert_(cert_file), client_ssl_filepath_client_key_(key_file),
    client_ssl_filepath_client_key_password_(key_file_password), connect_timeout_s_(connect_timeout_s),
    connect_opts_(MQTTClient_connectOptions_initializer)
{
    connect_ssl_opts_.enableServerCertAuth = 1;
    connect_ssl_opts_.ssl_error_cb         = openssl_error_callback;
    connect_ssl_opts_.ssl_error_context    = this;
    connect_ssl_opts_.trustStore           = client_ssl_filepath_ca_.c_str();
    connect_ssl_opts_.keyStore             = client_ssl_filepath_client_cert_.c_str();
    connect_ssl_opts_.privateKey           = client_ssl_filepath_client_key_.c_str();
    connect_ssl_opts_.privateKeyPassword   = client_ssl_filepath_client_key_password_.c_str();

    connect_opts_.keepAliveInterval = 20;
    connect_opts_.cleansession      = 1;
    connect_opts_.username          = client_username_.c_str();
    connect_opts_.password          = client_password_.c_str();
    connect_opts_.ssl               = const_cast<MQTTClient_SSLOptions *>(&connect_ssl_opts_);
    connect_opts_.connectTimeout    = connect_timeout_s_;
}

MQTTClientImplement::~MQTTClientImplement()
{
}

void MQTTClientImplement::connection_lost_callback(void *context, char *cause)
{
    auto pthis = static_cast<MQTTClientImplement *>(context);
    std::cout << "connection_lost_callback" << std::endl;
}

int MQTTClientImplement::message_arrived_callback(void *context, char *topicName, int topicLen, MQTTClient_message *message)
{
    auto pthis = static_cast<MQTTClientImplement *>(context);

    auto buffer = std::shared_ptr<char>(new char[message->payloadlen], std::default_delete<char[]>());
    memcpy(buffer.get(), message->payload, message->payloadlen);
    buffer.get()[message->payloadlen] = '\0';
    std::cout << "receive message: " << topicName << ", msg: " << buffer.get() << std::endl;
    return 1;
}

void MQTTClientImplement::delivery_complete_callback(void *context, MQTTClient_deliveryToken dt)
{
    auto pthis = static_cast<MQTTClientImplement *>(context);
    std::cout << "delivery_complete_callback" << std::endl;
}

int MQTTClientImplement::openssl_error_callback(const char *str, size_t len, void *u)
{
    std::cerr << "openssl_error_callback: " << str << std::endl;
    return 1;
}

bool MQTTClientImplement::connect()
{
    int ret = MQTTClient_create(&client_, server_uri_.c_str(), client_id_.c_str(), MQTTCLIENT_PERSISTENCE_NONE, NULL);
    if (ret != MQTTCLIENT_SUCCESS) {
        std::cerr << "Failed to create client, return code: " << ret << std::endl;
        return false;
    }

    ret = MQTTClient_setCallbacks(client_, this, connection_lost_callback, message_arrived_callback, delivery_complete_callback);
    if (ret != MQTTCLIENT_SUCCESS) {
        std::cerr << "Failed to set callbacks, return code: " << ret << std::endl;
        return false;
    }

    ret = MQTTClient_connect(client_, &connect_opts_);
    if (ret != MQTTCLIENT_SUCCESS) {
        std::cerr << "Failed to connect, return code: " << ret << std::endl;
        return false;
    }
    return true;
}

void MQTTClientImplement::disconnect()
{
    int ret = MQTTClient_disconnect(client_, 10000);
    if (ret != MQTTCLIENT_SUCCESS)
        std::cerr << "Failed to disconnect, return code: " << ret << std::endl;
    MQTTClient_destroy(&client_);
}

bool MQTTClientImplement::subscribe(const std::string &topic, int qos)
{
    int ret = MQTTClient_subscribe(client_, topic.c_str(), qos);
    if (ret != MQTTCLIENT_SUCCESS) {
        return false;
    }
    return true;
}

bool MQTTClientImplement::unsubscribe(const std::string &topic)
{
    int ret = MQTTClient_unsubscribe(client_, topic.c_str());
    if (ret != MQTTCLIENT_SUCCESS) {
        return false;
    }
    return true;
}

bool MQTTClientImplement::publish(const std::string &topic, const std::string &data, int qos)
{
    MQTTClient_message       message = MQTTClient_message_initializer;
    MQTTClient_deliveryToken token   = 0;
    message.payload                  = const_cast<char *>(data.c_str());
    message.payloadlen               = data.length();
    message.qos                      = qos;
    message.retained                 = 0;

    int ret = MQTTClient_publishMessage(client_, topic.c_str(), &message, &token);
    if (ret != MQTTCLIENT_SUCCESS) {
        std::cout << "Failed to publish, return code: " << ret << std::endl;
        return false;
    }
    return true;
}
